package com.gitee.fsmxm.thinking.in.concurrent.basic;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 死锁问题解决 （循环）
 * 发生条件：
 * 1. 破坏 互斥，互斥的问题中此条件无法破坏
 * 2. 破坏 占有且等待，可以一次性申请所有需要的资源
 * 3. 破坏 不可抢占，占用部分资源的线程进一步申请其他资源时，如果申请不到，可以主动释放它占有的资源
 * 4. 破坏 循环等待，可以靠按序申请资源来预防。所谓按序申请，是指资源是有线性顺序的，申请的时候可以先申请资源序号小的，再申请资源序号大的
 */
public class _03_DeadLock_Solution1 {
    /**
     * 账户类
     */
    static class Account {
        int balance; // 账户余额
        int id; // 账户id

        public Account(int balance, int id) {
            this.balance = balance;
            this.id = id;
        }

        void transfer(final Account target, int amount) {
            try {
                // 申请所有资源，破坏 占有且等待条件
                while (!Allocator.apply(this, target)) ;

                synchronized (this) { // 锁转出账户
                    if (balance > amount) {
                        synchronized (target) { // 锁转入账户
                            balance -= amount;
                            target.balance += amount;
                        }
                    }
                }
            } finally {
                Allocator.free(this, target); // 释放资源
            }
        }
    }

    /**
     * 资源分配器
     */
    static class Allocator {
        private Allocator() {}
        static Set<Object> pool = new HashSet<>();

        static synchronized boolean apply(Object... os) {
            if (containsOne(os)) {
                return false;
            }
            pool.addAll(Arrays.asList(os));
            return true;
        }

        static boolean containsOne(Object... os) {
            for (Object o : os) {
                if (pool.contains(o)) {
                    return true;
                }
            }
            return false;
        }

        static synchronized void free(Object... os) {
            Arrays.asList(os).forEach(pool::remove);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        final Account a1 = new Account(100, 1000);
        final Account a2 = new Account(300, 2000);

        // a1 给 a2 转 20块
        Thread t1 = new Thread(() -> a1.transfer(a2, 20));
        // a2 给 a1 转 50块
        Thread t2 = new Thread(() -> a2.transfer(a1, 50));

        // 两个人转账同时发生 （也可以断点模拟调试）
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println(a1.balance);
        System.out.println(a2.balance);
    }
}
